/*
 * @(#)SecurityConfig.java 2020-07-08
 *
 * Copyright (c) 2010 by rayootech.com. All rights reserved.
 */
package com.nuctech.formlogin.config;

import cn.hutool.core.io.IoUtil;
import cn.hutool.http.ContentType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nuctech.formlogin.filter.VerifyCodeFilter;
import com.nuctech.formlogin.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import sun.nio.cs.HistoricallyNamedCharset;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @author lilu
 * @date 2020-07-08
 * @since 1.0.0
 */
@Configuration
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	private final UserService userService;
	private final VerifyCodeFilter verifyCodeFilter;

	@Bean
	public PasswordEncoder passwordEncoder() {
		return NoOpPasswordEncoder.getInstance();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(this.userService);
	}

	/**
	 * 角色层次关系说明
	 * @return
	 */
	@Bean
	public RoleHierarchy roleHierarchy() {
		RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
		// admin角色大于root角色，即admin角色拥有root的权限
		hierarchy.setHierarchy("ROLE_admin > ROLE_root");
		return hierarchy;
	}

	@Override
	public void configure(WebSecurity web) throws Exception {
		List<String> antPatterns = new ArrayList<>();

		antPatterns.add("/css/**");
		antPatterns.add("/js/**");
		antPatterns.add("/images/**");
		antPatterns.add("/fonts/**");
		antPatterns.add("/verifycode");
		antPatterns.add("/favicon.ico");

		web.ignoring().antMatchers(antPatterns.toArray(new String[antPatterns.size()]));
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// 在用户名密码认证过滤器之前加入验证码过滤器
		http.addFilterBefore(this.verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
		http.authorizeRequests()
			// AR--------------- 配置顺序很重要啊
			.antMatchers("/admin/**").hasRole("admin")
			.antMatchers("/root/**").hasRole("root")
			// AR--------------- anyRequest只能写在最后
			.anyRequest().authenticated()
			// AR---------------
			.and()
			.formLogin()
			.loginPage("/login.html")
			.loginProcessingUrl("/doLogin")
			.usernameParameter("name")
			.passwordParameter("pass")
			// 登录成功处理逻辑
			.successHandler((request, response, authentication) -> {
				response.setContentType(ContentType.JSON.toString(StandardCharsets.UTF_8));
				// 当前登录用户信息
				Object userInfo = authentication.getPrincipal();
				PrintWriter out = response.getWriter();
				out.write(new ObjectMapper().writeValueAsString(userInfo));
				out.flush();
				out.close();
			})
			// 登录失败处理逻辑
			.failureHandler((request, response, exception) -> {
				response.setContentType(ContentType.JSON.toString(StandardCharsets.UTF_8));
				PrintWriter out = response.getWriter();
				String message = "网络异常，请稍后再试！";

				if (exception instanceof LockedException) {
					message = "账户被锁定，请联系管理员！";
				} else if (exception instanceof CredentialsExpiredException) {
					message = "密码过期，请联系管理员！";
				} else if (exception instanceof AccountExpiredException) {
					message = "账户过期，请联系管理员！";
				} else if (exception instanceof DisabledException) {
					message = "账户被禁用，请联系管理员！";
				} else if (exception instanceof BadCredentialsException) {
					message = "用户名或密码错误，请重新输入！";
				}

				out.write(new ObjectMapper().writeValueAsString(message));
				out.flush();
				out.close();
			})
			.permitAll()
			.and()
			.logout()
			.logoutUrl("/logout")
			// 注销成功处理逻辑
			.logoutSuccessHandler((request, response, authentication) -> {
				response.setContentType(ContentType.JSON.toString(StandardCharsets.UTF_8));
				PrintWriter out = response.getWriter();
				String message = "注销登录成功！";
				out.write(new ObjectMapper().writeValueAsString(message));
				out.flush();
				out.close();
			})
			.deleteCookies()
			.clearAuthentication(true)
			.invalidateHttpSession(true)
			.permitAll()
			.and()
			.csrf().disable()
			.exceptionHandling()
			// 权限不足处理逻辑
			.accessDeniedHandler((request, response, exception) -> {
				response.setContentType(ContentType.JSON.toString(StandardCharsets.UTF_8));
				PrintWriter out = response.getWriter();
				String message = "你没有访问该功能的权限！";
				out.write(new ObjectMapper().writeValueAsString(message));
				out.flush();
				out.close();
			})
			// 认证失败处理逻辑
			.authenticationEntryPoint((request, response, exception) -> {
				response.setContentType(ContentType.JSON.toString(StandardCharsets.UTF_8));
				PrintWriter out = response.getWriter();
				String message = "该功能需要登录之后才能使用！";
				out.write(new ObjectMapper().writeValueAsString(message));
				out.flush();
				out.close();
			});
	}
}


